import { Injectable } from '@angular/core';
import {
  HttpClient,
  HttpEvent,
  HttpEventType,
  HttpRequest,
  HttpResponse,
  HttpHeaders,
} from '@angular/common/http';
import { BehaviorSubject } from 'rxjs';
import { UploadOptions, UploadFile } from './interface';
import { fuiMessageService } from '../message/message.module';

@Injectable()
export class UploaderService {

  uploadOptions: UploadOptions = null;

  fileSubj = new BehaviorSubject<File | UploadFile>(null);

  fileList: UploadFile[] = [];

  constructor(
    private http: HttpClient,
    private message: fuiMessageService,
  ) { }

  /** Upload a single file. */
  upload(file: File | UploadFile) {
    if (!file) {
      return;
    }

    if (!this.checkFileSizeAndType(file)) {
      return;
    }

    this.fileSubj.next(file);

    let uploadFile: UploadFile = null;
    if (file instanceof File) {
      uploadFile = this.fileToObject(file);
      this.onStart(uploadFile);
    } else {
      uploadFile = file;
    }

    const headers = {
      'X-Requested-With': 'XMLHttpRequest',
    };

    const req = new HttpRequest('POST', this.uploadOptions.action, uploadFile.originFileObj, {
      reportProgress: true,
      headers: new HttpHeaders(headers),
    });
    const subscription = this.http.request(req).subscribe((event: HttpEvent<Record<string, unknown>>) => {
      let percent = 0;
      if (event.type === HttpEventType.UploadProgress) {
        if (event.total > 0) {
          percent = event.loaded / event.total * 100;
        }
        this.onProgress(uploadFile, percent);
      } else if (event instanceof HttpResponse) {
        this.onSuccess(uploadFile);
      }
    }, (err) => {
      this.onError(uploadFile, err);
    });
    uploadFile.subscriptions.push(subscription);
  }

  /** Remove a file item. */
  removeFileItem(file: UploadFile) {
    this.fileList = this.fileList.filter(item => item.uid !== file.uid);
  }

  private checkFileSizeAndType(file: File | UploadFile): boolean {
    if (this.uploadOptions.fileSize && this.uploadOptions.fileSize < file.size * 1024) {
      this.message.error(this.uploadOptions.fileSizeErrorText);
      return false;
    }
    if (!this.uploadOptions.fileTypes) {
      return true;
    }
    for (const fileType of this.uploadOptions.fileTypes) {
      if (fileType === file.type) {
        return true;
      }
    }
    this.message.error(this.uploadOptions.fileTypesErrorText);
    return false;
  }

  private getFileItem(file: UploadFile, fileList: UploadFile[]): UploadFile {
    return fileList.filter(item => item.uid === file.uid)[0];
  }

  private fileToObject(file: File): UploadFile {
    return {
      name: file.name,
      size: file.size,
      type: file.type,
      uid: Math.random().toString(36).substring(2),
      response: null,
      error: null,
      errorText: '',
      percent: 0,
      status: 'active',
      originFileObj: file as any,
      subscriptions: [],
    };
  }

  private onStart(file: UploadFile): void {
    this.fileList.push(file);
  }

  private onProgress(file: UploadFile, percent: number): void {
    const targetItem = this.getFileItem(file, this.fileList);
    targetItem.percent = percent;
  }

  private onSuccess(file: UploadFile): void {
    const targetItem = this.getFileItem(file, this.fileList);
    targetItem.status = 'success';
  }

  private onError(file: UploadFile, e) {
    const targetItem = this.getFileItem(file, this.fileList);
    targetItem.status = 'exception';
    targetItem.error = e;
    targetItem.errorText = `${targetItem.name}(${e.statusText} ${e.status})`;
  }
}
